/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.myfaces.trinidadinternal.ui.action;

import java.io.IOException;

import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.apache.myfaces.trinidadinternal.share.url.FormEncoder;
import org.apache.myfaces.trinidadinternal.share.url.URLEncoder;

import org.apache.myfaces.trinidadinternal.ui.UIXRenderingContext;
import org.apache.myfaces.trinidadinternal.ui.UIConstants;
import org.apache.myfaces.trinidadinternal.ui.UINode;

import org.apache.myfaces.trinidadinternal.ui.beans.MarlinBean;

import org.apache.myfaces.trinidadinternal.ui.collection.Parameter;

import org.apache.myfaces.trinidadinternal.ui.data.BoundValue;

import org.apache.myfaces.trinidadinternal.ui.data.bind.AccessKeyBoundValue;

// =-=ags We probably should not depend on the following LAF classes!
import org.apache.myfaces.trinidadinternal.ui.laf.base.SkinTranslatedBoundValue;
import org.apache.myfaces.trinidadinternal.ui.laf.base.xhtml.FormValueRenderer;
import org.apache.myfaces.trinidadinternal.ui.laf.base.xhtml.XhtmlLafRenderer;
import org.apache.myfaces.trinidadinternal.ui.laf.base.xhtml.XhtmlLafUtils;

import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.jsLibs.Scriptlet;

import org.apache.myfaces.trinidadinternal.ui.partial.PartialPageRendererUtils;

/**
 * An implementation of FireAction which supports Partial Page Rendering.
 *
 * @version $Name:  $ ($Revision: 245 $) $Date: 2008-11-26 00:05:42 +0000 (Wed, 26 Nov 2008) $
 * @deprecated This class comes from the old Java 1.2 UIX codebase and should not be used anymore.
 */
@Deprecated
public class FirePartialAction extends FireAction
{
  /**
   * Creates a FirePartialAction with the no partial targets which will fire an
   * update event by submiting the enclosing form.
   */
  public FirePartialAction()
  {
    this(null, true, null, null);
  }

  /**
   * Creates a FirePartialAction with the specified partial targets which
   * will submit the enclosing form.
   *
   * @param event   The name of the event that should be sent when
   *                the FirePartialAction is activated.  If
   *                null, the event name defaults to "update".
   */
  public FirePartialAction(String event)
  {
    this(event, true, null, null);
  }


  /**
   * Creates a FirePartialAction with the specified partial targets which
   * will submit the enclosing form. This constructor will use the default
   * event and submit parameters ("Update" and true).
   *
   * @param targets The IDs of the partial target nodes to update in
   *                response to the event generated by this action.
   */
  public FirePartialAction(String[] targets)
  {
    this(null, true, targets, null);
  }


  /**
   * Creates a FirePartialAction with the specified partial
   * targets and event name which will submit the enclosing form.
   *
   * @param event   The name of the event that should be sent when
   *                the FirePartialAction is activated.  If
   *                null, the event name defaults to "update".
   * @param submit  Whether or not a form submit will be used to fire the event
   */
  public FirePartialAction(
    String   event,
    boolean  submit
    )
  {
    this(event, submit, null, null);
  }

  /**
   * Creates a FirePartialAction with the specified partial
   * targets and event name.
   *
   * @param event   The name of the event that should be sent when
   *                the FirePartialAction is activated.  If
   *                null, the event name defaults to "update".
   * @param submit  Whether or not a form submit will be used to fire the event
   * @param targets The IDs of the partial target nodes to update in
   *                response to the event generated by this action.
   */
  public FirePartialAction(
    String   event,
    boolean  submit,
    String[] targets
    )
  {
    this(event, submit, targets, null);
  }

  /**
   * Creates a FirePartialAction with the specified partial
   * targets and event name.
   *
   * @param event   The name of the event that should be sent when
   *                the FirePartialAction is activated.  If
   *                null, the event name defaults to "update".
   * @param submit  Whether or not a form submit will be used to fire the event
   * @param targets The IDs of the partial target nodes to update in
   *                response to the event generated by this action.
   * @param triggerText The text that will be rendered on the ClientAction
   *                    button or link if the target browser does not support
   *                    partial page rendering, and a button or link has to be
   *                    rendered to trigger the action.
   */
  public FirePartialAction(
    String   event,
    boolean  submit,
    String[] targets,
    String   triggerText
    )
  {
     setEvent(event);
     setFormSubmitted(submit);
     setTargets(targets);
     setTriggerText(triggerText);
  }

  /**
   * Implementation of FireAction.getSubmitScript().
   */
  @Override
  protected String getSubmitScript
    (UIXRenderingContext context,
     UINode node,
     String returnScript)
  {
    String startScript = "_uixspu(\'";
    String endScript = ");";

    // Get the formName
    String formName = ActionUtils.getFormName(context);
    if (formName == null)
      return null;

    // Get the source attr...
    String source = getSource(context, node);

    boolean unvalidated = isUnvalidated(context);
    Parameter[] parameters = getParameters();
    String event = getEventValue(context);

    boolean isPartial = XhtmlLafRenderer.supportsPartialRendering(context);
    String partialTargets = null;
    if (isPartial)
      partialTargets = _getPartialTargets(context);

    // Figure out how big our buffer needs to be
    int length = getBufferSize(context,
                               startScript,
                               endScript,
                               null,
                               formName,
                               event,
                               source,
                               partialTargets,
                               null, null, null,
                               returnScript,
                               parameters);

    // Create the buffer
    StringBuilder buffer = new StringBuilder(length);

    // Build up the script
    buffer.append(startScript);
    buffer.append(formName);
    buffer.append("\',");
    buffer.append(unvalidated ? "0" : "1");

    URLEncoder encoder = context.getURLEncoder();
    String eventKey = encoder.encodeParameter(UIConstants.EVENT_PARAM);
    String sourceKey = encoder.encodeParameter(UIConstants.SOURCE_PARAM);
    String partialTargetsKey = encoder.encodeParameter(
                                         UIConstants.PARTIAL_TARGETS_PARAM);
    FormEncoder formEncoder = context.getFormEncoder();
    String encodedEvent = XhtmlLafUtils.
                                    getFormEncodedParameter(formEncoder,
                                                            formName,
                                                            eventKey,
                                                            event);

    String encodedSource = XhtmlLafUtils.
                                    getFormEncodedParameter(formEncoder,
                                                            formName,
                                                            sourceKey,
                                                            source);
    String encodedPartialTargets = XhtmlLafUtils.
                                    getFormEncodedParameter(formEncoder,
                                                            formName,
                                                            partialTargetsKey,
                                                            partialTargets);

    appendJSParameter(buffer, encodedEvent);
    appendJSParameter(buffer, encodedSource);
    appendJSParameter(buffer, encodedPartialTargets);

    // Note: This value is not FormEncoded because _uixspu needs it in a boolean
    // form, and because it is never sent back from the client to the server.
    _appendBooleanParameter(buffer, isPartial);

    // need to pass formName for FormEncoder
    ActionUtils.appendClientParameters(context, buffer, parameters,
                                       null /*event*/, null /*source*/,
                                       formName);

    buffer.append(endScript);
    buffer.append(returnScript);

    return buffer.toString();
  }

  @Override
  protected String getChangeScript
    (UIXRenderingContext context,
     UINode node,
     String returnScript)
  {
    // First off, check to see if we are doing partial page rendering -
    // it might not be supported...
    boolean isPartial = XhtmlLafRenderer.supportsPartialRendering(context);

    // Assign some vars based on whether we are doing full or partial
    String startScript = FULL_START_SCRIPT;
    String endScript = FULL_END_SCRIPT;
    String partialTargets = null;
    String partialTargetsKey = null;

    if (isPartial)
    {
      startScript = _PARTIAL_START_SCRIPT;
      endScript = _PARTIAL_END_SCRIPT;
      partialTargets = _getPartialTargets(context);
      partialTargetsKey = _getPartialTargetsKey(context.getURLEncoder(),
                                                partialTargets);
    }

    // Now let the main change script builder build the actual script
    return getChangeScript(context, node, returnScript,
                           startScript, endScript,
                           partialTargetsKey, partialTargets);

  }

  /**
   * Returns the set of partial targets that are updated when this
   * ClientAction is activated.
   */
  public final String[] getTargets()
  {
    return ActionUtils.copyPartialTargets(_targets);
  }

  /**
   * Returns the set of partial targets that are updated when this
   * ClientAction is activated.
   */
  public final String getTriggerText()
  {
    return _triggerText;
  }

  /**
   * Override of FireAction.renderAsEvent().
   */
  @Override
  public boolean renderAsEvent(
    UIXRenderingContext context,
    UINode           node)
  {
    return true;
  }

  /**
   * Sets the partial targets to update.
   */
  public final void setTargets(String[] partialTargets)
  {
    _targets = (partialTargets == null) ? null : partialTargets.clone();
  }

  /**
   * Sets a BoundValue that will dynamically evaluate the partial
   * targets.
   */
  public void setTargetsBinding(BoundValue partialTargetsBinding)
  {
    _targetsBinding = partialTargetsBinding;
  }

  /**
   * Sets the text that will be rendered on the ClientAction button or link if
   * the target browser does not support partial page rendering, and a button
   * or link has to be rendered to trigger the action.
   */
  public final void setTriggerText(String triggerText)
  {
    _triggerText = triggerText;
  }

  /**
   * Override of ClientAction.writeDependencies().
   */
  @Override
  public void writeDependencies(
    UIXRenderingContext context,
    UINode node
    ) throws IOException
  {
    super.writeDependencies(context, node);

    if (isFormSubmitted())
    {
      // Render our scriptlet
      XhtmlLafUtils.addLib(context, _FIRE_PARTIAL_ACTION_SCRIPTLET);

      if (XhtmlLafRenderer.supportsPartialRendering(context))
      {
        // Render hidden fields needed for PPR
        String formName = ActionUtils.getFormName(context);
        if (formName != null)
        {
          URLEncoder encoder = context.getURLEncoder();
          String partialTargetsKey
            = encoder.encodeParameter(UIConstants.PARTIAL_TARGETS_PARAM);
          String partialKey
            = encoder.encodeParameter(UIConstants.PARTIAL_PARAM);

          FormValueRenderer.addNeededValue(context,
                                           formName,
                                           partialTargetsKey,
                                           partialKey,
                                           null, null);
        }
      }
    }
  }


  /**
   * Override of ClientAction.isTriggerRequired()
   */
  @Override
  public boolean isTriggerRequired(
    UIXRenderingContext context,
    UINode           node
    )
  {
    // We use an explicit trigger when PPR is not supported, since
    // we don't want to do full page refreshes in response to
    // interactions such as selecting an item from a choice or
    // tabbing out of a text field.
    return !XhtmlLafRenderer.supportsPartialRendering(context);
  }

  /**
   * Override of ClientAction.renderTrigger().
   */
  @Override
  public void renderTrigger(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    Object text = _triggerText;
    Object accessKey = null;

    if (text == null)
    {

      BoundValue buttonTextandAccessKeyBV =
        new SkinTranslatedBoundValue("PPR_TRIGGER_LABEL");


      text = new AccessKeyBoundValue( buttonTextandAccessKeyBV, false);

      accessKey = new AccessKeyBoundValue( buttonTextandAccessKeyBV, true);
    }

    renderTrigger(context, node, text, accessKey);
  }

  /**
   * Override of ClientAction.renderTrigger().
   */
  public void renderTrigger(
    UIXRenderingContext context,
    UINode           node,
    Object           text,
    Object           accessKey
    ) throws IOException
  {
    // We render a button as our trigger when PPR is not supported.
    String script = getScript(context, node, Boolean.FALSE);

    // =-=ags We could use a singleton here, but I'm not sure that
    //        performance is actually an issue.
    // =-=ags We should probably provide description text for the button.
    MarlinBean button = new MarlinBean(UIConstants.BUTTON_NAME);
    button.setAttributeValue(UIConstants.TEXT_ATTR, text);
    button.setAttributeValue(UIConstants.ACCESS_KEY_ATTR, accessKey);
    button.setOnClick(script);

    button.render(context);
  }

  // Appends a parameter to our buffer
  private static void _appendBooleanParameter(
    StringBuilder buffer,
    boolean      value
    )
  {
    if (buffer.charAt(buffer.length() - 1) != ',')
      buffer.append(",");

    buffer.append(value ? "1" : "0");
  }

  @Override
  public Parameter[] getParameters(
    UIXRenderingContext context,
    UINode           node)
  {

    // Get the encoded names for our event parameters
    URLEncoder encoder = context.getURLEncoder();
    String targets = _getPartialTargets(context);

    Parameter[] allParams = super.getParameters(context, node);
    if (targets != null)
    {
      String targetsKey = _getPartialTargetsKey(encoder, targets);
      String partialKey = encoder.encodeParameter(UIConstants.PARTIAL_PARAM);

      Parameter[] localParams = new Parameter[2];

      localParams[0] = ActionUtils.buildParameter(context, node,
                                                  null, "true",
                                                  partialKey);
      localParams[1] = ActionUtils.buildParameter(context, node,
                                                  null, targets,
                                                  targetsKey);

      allParams = ActionUtils.joinParameterArrays(localParams, allParams);
    }

    return allParams;
  }

  // Returns the set of partial targets encoded as a string
  private String _getPartialTargets(UIXRenderingContext context)
  {
    // Get the partial targets
    String[] targets = (String[])ActionUtils.getValue(context,
                                                      _targetsBinding,
                                                      _targets);

    // Return the encoded value
    return PartialPageRendererUtils.encodePartialTargets(targets);
  }


  // Returns the partialTargets event parameter key, if necessary
  private static String _getPartialTargetsKey(
    URLEncoder encoder,
    String partialTargets
    )
  {
    if (partialTargets == null)
      return null;

    return encoder.encodeParameter(UIConstants.PARTIAL_TARGETS_PARAM);
  }

  // Scriptlet that renders the submitPartialUpdate script
  private static class FirePartialActionScriptlet extends Scriptlet
  {
    static public Scriptlet sharedInstance()
    {
      return _sInstance;
    }

    @Override
    public Object getScriptletKey()
    {
      return _FIRE_PARTIAL_ACTION_SCRIPTLET;
    }

    @Override
    protected void outputScriptletContent(
      FacesContext context,
      RenderingContext arc)
      throws IOException
    {
      // Make sure we have a form
      if (arc.getFormData() == null)
        return;

      String formName = arc.getFormData().getName();
      if (formName == null)
        return;

      // We output a function "_uixspu" (UIX Submit Partial Update)
      // which takes the following arguments:
      // - f:  The form name
      // - v:  The validation flag
      // - e:  The event name (defaults to "update")
      // - s:  The source parameter
      // - pt: The partial targets parameter
      // - p:  boolean indicating whether this is partial or full
      // - o:  Object containing client-defined parameters


      // To support redirecting on IE, we need to check if the parent page is
      // actually responding to a partial update call, so we keep track of it
      // here. We don't worry about re-setting this to false because the only
      // time it comes into play is when the back button is causing a re-render
      // of the iframe and, hence, a re-execution of the redirect script. In
      // that case, we'll have a new javascript context, and _pprUpdateMode will
      // be invalid, or will be initialized to false.
      ResponseWriter writer = context.getResponseWriter();
      writer.writeText("var _pprUpdateMode=false;", null);
      writer.writeText("function _uixspu(f,v,e,s,pt,p,o){", null);
      writer.writeText("_pprUpdateMode=true;", null);
      writer.writeText("if(!o)o=new Object();", null);
      writer.writeText("o.", null);
      writer.writeText(UIConstants.EVENT_PARAM, null);
      // Should this 'update' value be formEncoded?
      writer.writeText("=(e)?e:\'update\';", null);
      writer.writeText("if(s)o.", null);
      writer.writeText(UIConstants.SOURCE_PARAM, null);
      writer.writeText("=s;if(p){o.", null);
      writer.writeText(UIConstants.PARTIAL_PARAM, null);
      // Should this 'true' value be formEncoded?
      writer.writeText("=\'true\';if(pt)o.", null);
      writer.writeText(UIConstants.PARTIAL_TARGETS_PARAM, null);
      writer.writeText("=pt;_submitPartialChange(f,v,o);}else ", null);
      writer.writeText("submitForm(f,v,o);}", null);
    }

    private static FirePartialActionScriptlet _sInstance =
      new FirePartialActionScriptlet();
  }

  // Name for our Scriptlet
  private static final String _FIRE_PARTIAL_ACTION_SCRIPTLET =
    "FirePartialAction";

  static
  {
    // Register our scriptlet
    FirePartialActionScriptlet.sharedInstance().registerSelf();
  }

  private String[]   _targets;
  private BoundValue _targetsBinding;

  private String     _triggerText;

  private static final String _PARTIAL_START_SCRIPT = "_firePartialChange(\'";
  private static final String _PARTIAL_END_SCRIPT   = "\');";
}
